// xhr-moz-chunked-loader.js-MozChunkedLoader类-火狐加载器
import Log from '../utils/logger.js';
import {BaseLoader, LoaderStatus, LoaderErrors} from './loader.js';
import {RuntimeException} from '../utils/exception.js';

// For FireFox browser which supports `xhr.responseType = 'moz-chunked-arraybuffer'`
class MozChunkedLoader extends BaseLoader {
    // 属性
    // isSupported() 是否支持火狐，核心是判断 XMLHttpRequest 对象的响应类型是不是 moz-chunked-arraybuffer
    static isSupported() {
        try {
            let xhr = new XMLHttpRequest();
            // Firefox 37- requires .open() to be called before setting responseType
            xhr.open('GET', 'https://example.com', true);
            xhr.responseType = 'moz-chunked-arraybuffer';
            return (xhr.responseType === 'moz-chunked-arraybuffer');
        } catch (e) {
            Log.w('MozChunkedLoader', e.message);
            return false;
        }
    }
    constructor(seekHandler, config) {
        super('xhr-moz-chunked-loader');
        // TAG 实例构造器名称
        this.TAG = 'MozChunkedLoader';
        // _seekHandler 搜索处理函数
        this._seekHandler = seekHandler;
        this._config = config;
        // _needStash 需要暂存区，重写为 true
        this._needStash = true;
        // _xhr XMLHttpRequest 实例
        this._xhr = null;
        // _requestAbort 请求终止标志位，初始化为 false
        this._requestAbort = false;
        // _contentLength 内容长度，初始化为 null
        this._contentLength = null;
        // _receivedLength 已接收长度，初始化为 0
        this._receivedLength = 0;
    }
    // 方法
    // 销毁实例
    destroy() {
        // 如果实例处于工作中，终止加载
        if (this.isWorking()) {
            this.abort();
        }
        // 释放实例的 XMLHttpRequest 实例
        if (this._xhr) {
            this._xhr.onreadystatechange = null;
            this._xhr.onprogress = null;
            this._xhr.onloadend = null;
            this._xhr.onerror = null;
            this._xhr = null;
        }
        // 执行基类的 destroy 方法
        super.destroy();
    }
    // 打开数据源开始加载
    open(dataSource, range) {
        // 设置实例的数据源为 dataSource，实例的范围为 range
        this._dataSource = dataSource;
        // 获取实例的搜索处理函数配置（之后简称搜索配置）
        this._range = range;

        let sourceURL = dataSource.url;
        if (this._config.reuseRedirectedURL && dataSource.redirectedURL != undefined) {
            sourceURL = dataSource.redirectedURL;
        }

        let seekConfig = this._seekHandler.getConfig(sourceURL, range);
        this._requestURL = seekConfig.url;
        // new 一个 XMLHttpRequest 实例（之后简称 xhr），保存到实例的 XMLHttpRequest 实例
        let xhr = this._xhr = new XMLHttpRequest();
        // 通过 xhr 的 open 方法进行 ajax，传入搜索配置的 url
        xhr.open('GET', seekConfig.url, true);
        // 设置 xhr 的 responseType 为 moz-chunked-arraybuffer
        xhr.responseType = 'moz-chunked-arraybuffer';
        // 对 xhr 的四个事件进行监听
        xhr.onreadystatechange = this._onReadyStateChange.bind(this);
        xhr.onprogress = this._onProgress.bind(this);
        xhr.onloadend = this._onLoadEnd.bind(this);
        xhr.onerror = this._onXhrError.bind(this);
        // 如果 dataSource 的证书属性为 true
        if (dataSource.withCredentials) {
            // 设置 xhr 的 withCredentials 属性为 true
            xhr.withCredentials = true;
        }
        //  如果搜索配置头是一个对象
        if (typeof seekConfig.headers === 'object') {
            let headers = seekConfig.headers;
            // 遍历搜索配置头
            for (let key in headers) {
                if (headers.hasOwnProperty(key)) {
                    // 通过 setRequestHeader 方法将它自身的键值对设置到 xhr 的请求头中
                    xhr.setRequestHeader(key, headers[key]);
                }
            }
        }
        // 添加附加headers
        if (typeof this._config.headers === 'object') {
            let headers = this._config.headers;

            for (let key in headers) {
                if (headers.hasOwnProperty(key)) {
                    xhr.setRequestHeader(key, headers[key]);
                }
            }
        }
        // 设置实例的状态为连接中
        this._status = LoaderStatus.kConnecting;
        // 启动 xhr 连接
        xhr.send();
    }
    // 终止加载
    abort() {
        // 设置当前实例的请求终止标志位为 true
        this._requestAbort = true;
        // 如果实例上存在 XMLHttpRequest 实例，终止之
        if (this._xhr) {
            this._xhr.abort();
        }
        // 设置实例的状态为完成
        this._status = LoaderStatus.kComplete;
    }
    // readyState 属性改变事件处理函数
    _onReadyStateChange(e) {
        // 从 e 从获取 xhr
        let xhr = e.target;
        // 如果 xhr 的 readyState 等于 2
        if (xhr.readyState === 2) {  // HEADERS_RECEIVED
            if (xhr.responseURL != undefined && xhr.responseURL !== this._requestURL) {
                if (this._onURLRedirect) {
                    let redirectedURL = this._seekHandler.removeURLParameters(xhr.responseURL);
                    this._onURLRedirect(redirectedURL);
                }
            }
            // 如果 xhr 的 status 是 2xx
            if (xhr.status !== 0 && (xhr.status < 200 || xhr.status > 299)) {
                // 设置实例的状态为出错
                this._status = LoaderStatus.kError;
                // 如果实例存在出错事件处理函数，执行之，传入 HTTP 状态码错误和错误信息
                if (this._onError) {
                    this._onError(LoaderErrors.HTTP_STATUS_CODE_INVALID, {code: xhr.status, msg: xhr.statusText});
                } else {
                    // 否则报 Moz HTTP 状态码无效错误
                    throw new RuntimeException('MozChunkedLoader: Http code invalid, ' + xhr.status + ' ' + xhr.statusText);
                }
            } else {
                // 否则设置实例的状态为缓冲中
                this._status = LoaderStatus.kBuffering;
            }
        }
    }
    // 进度事件处理函数
    _onProgress(e) {
        if (this._status === LoaderStatus.kError) {
            // Ignore error response
            return;
        }
        // 如果实例的内容长度不为空且 e.total 不为空不为零
        if (this._contentLength === null) {
            if (e.total !== null && e.total !== 0) {
                // 设置实例的内容长度为 e.total
                this._contentLength = e.total;
                // 如果实例存在已知内容长度事件处理函数，执行之，传入实例的内容长度
                if (this._onContentLengthKnown) {
                    this._onContentLengthKnown(this._contentLength);
                }
            }
        }
        // 将实例的接收长度加上 e.target.response 的字节长度
        let chunk = e.target.response;
        let byteStart = this._range.from + this._receivedLength;
        this._receivedLength += chunk.byteLength;
        // 如果实例存在完成数据到达处理函数，执行之，传入 e.target.response、实例范围起点 + 实例接收长度、实例接收长度
        if (this._onDataArrival) {
            this._onDataArrival(chunk, byteStart, this._receivedLength);
        }
    }
    // 加载停止事件处理函数
    _onLoadEnd(e) {
        // 如果实例的请求终止标志位为 true
        if (this._requestAbort === true) {
            // 设置实例的请求终止标志位为 false
            this._requestAbort = false;
            // 结束
            return;
        // 如果实例状态为出错，结束
        } else if (this._status === LoaderStatus.kError) {
            return;
        }
        // 设置实例状态为完成
        this._status = LoaderStatus.kComplete;
        // 如果实例存在完成事件处理函数，执行之，传入实例范围和实例范围 + 实例接收范围 - 1
        if (this._onComplete) {
            this._onComplete(this._range.from, this._range.from + this._receivedLength - 1);
        }
    }
    // 出错事件处理函数
    _onXhrError(e) {
        // 设置实例状态为出错
        this._status = LoaderStatus.kError;
        let type = 0;
        let info = null;
        // 如果 e.loaded 小于实例内容长度，错误类型为过早结束，错误信息填入过早结束文案
        if (this._contentLength && e.loaded < this._contentLength) {
            type = LoaderErrors.EARLY_EOF;
            info = {code: -1, msg: 'Moz-Chunked stream meet Early-Eof'};
        // 否则，错误类型为其他错误
        } else {
            type = LoaderErrors.EXCEPTION;
            info = {code: -1, msg: e.constructor.name + ' ' + e.type};
        }
        // 如果实例存在出错事件处理函数，执行之，传入错误类型和错误信息
        if (this._onError) {
            this._onError(type, info);
        // 否则抛出运行时错误
        } else {
            throw new RuntimeException(info.msg);
        }
    }

}

export default MozChunkedLoader;